package com.sie.bank;

import javax.crypto.Cipher;

import com.sie.zhq.tool.ByteTool;
import com.sie.zhq.tool.StringUtil;

/**
 * 测试生成ARQC,ARPC,MAC,CVN,CVN2
 *
 *
 */
public class BankAuthTest {
	/**
	 * 生成ARQC本质就是生成MAC,MAC值是取得的前4个字节的值
	 *
	 * @param pan
	 * @param panSN
	 * @param hexATC
	 * @param mainKey
	 * @param arqcDataSource
	 * @return
	 * @throws Exception
	 */
	public static String generateARQC(String pan, String panSN, String hexATC,
									  String mainKey, String arqcDataSource) throws Exception {

		if (null == mainKey || mainKey.length() != 32) {
			throw new IllegalArgumentException("invalid main key");
		}
		if (pan == null || pan.equals("") || pan.length() == 0) {
			throw new IllegalArgumentException("card no is null");
		}

		String processKey = generateProcesKey(pan, panSN, hexATC, mainKey);

		String result = process(processKey, arqcDataSource);

		if (result.length() != 16) {
			throw new IllegalArgumentException("mac invalid");
		}
		return result;
	}

	/**
	 * 根据ARQC来生成ARPC
	 *hexArqc表示ARQC的值8字节
	 * @param
	 * @param pan
	 *            :表示卡号
	 * @param panSN
	 *            :表示卡序号00或01
	 * @param hexATC
	 *            :交易记数器
	 * @param mainKey
	 *            :工作主密钥
	 * @param authCode
	 *            :授权响应码2个字节
	 * @return
	 * @throws Exception
	 */

	public static String generateARPC(String hexArqc, String pan, String panSN,
									  String hexATC, String mainKey, String authCode) throws Exception {

		if (null == mainKey || mainKey.length() != 32) {
			throw new IllegalArgumentException("非法的工作主密钥的值");
		}
		if (pan == null || pan.equals("") || pan.length() == 0) {
			throw new IllegalArgumentException("卡号不能为空，请检查传入的卡号");
		}

		if (hexArqc == null || hexArqc.equals("") || pan.length() == 0
				|| hexArqc.length() != 16) {
			throw new IllegalArgumentException("非法的ARQC数据");
		}

		String processKey = generateProcesKey(pan, panSN, hexATC, mainKey);

		String paddARC = StringUtil.padding(authCode, "right", "0", 16);

		String arqcAndARCXORResult = StringUtil.XOR(hexArqc, paddARC);

		String arpc = EncryptUnit.encryptDesSede(processKey,
				arqcAndARCXORResult);

		//System.out.println(arpc);

		return arpc;
	}

	/**
	 * 生成MAC，并获取计算后的前4个字节 命令中需要加密的数据加密以后再计算MAC。MAC使用对称密钥算法计算的， 步骤如下： 步骤1：初始值为8
	 * 字节全零（此步骤可省略）； 步骤2：下列数据按顺序排列得到一个数据块D： ——CLA、INS、P1、P2 和Lc（Lc 的长度包括MAC
	 * 的长度）； ——ATC（对于发卡行脚本处理，此ATC 在请求中报文中上送）；
	 * ——应用密文（对于发卡行脚本处理，此应用密文通常是ARQC，或AAC，在请求报文中上送）； ——命令数据域中的明文或密文数据（如果存在）。
	 * 步骤3：将上述数据块D 分成8 字节长的数据块D1、D2、D3…最后一块数据块的字节长度为1 到8； 步骤4：如果最后一块数据块的长度为8
	 * 字节，后面补8 字节数据块：80 00 00 00 00 00 00 00， 执行步骤5； 如果最后一块数据块的长度小于8
	 * 字节，后面补一个字节80，如果长度到8 字节，执行 步骤5。如果仍然不够8 字节，补00 直到8 字节； 步骤5：用MAC
	 * 过程密钥对数据块进行加密。MAC 过程密钥的生成见C.4； 图C.1 是使用MAC 过程密钥A 和B 生成MAC 的流程图。 步骤6：MAC
	 * 的计算结果为8 字节，从最左边的字节开始取4 字节
	 *
	 * @param pan
	 * @param panSN
	 * @param hexATC
	 * @param mainKey
	 * @param macDataSource
	 * @return
	 * @throws Exception
	 */

	public static String generateMAC(String pan, String panSN, String hexATC,
									 String mainKey, String macDataSource) throws Exception {

		if (null == mainKey || mainKey.length() != 32) {
			throw new IllegalArgumentException("非法的工作主密钥的值");
		}

		if (pan == null || pan.equals("") || pan.length() == 0) {
			throw new IllegalArgumentException("卡号不能为空，请检查传入的卡号");
		}

		String processKey = generateProcesKey(pan, panSN, hexATC, mainKey);

		String result = process(processKey, macDataSource);

		if (result.length() != 16) {
			throw new IllegalArgumentException("返回的mac结果非8字节(16位hex)");
		}

		result = result.substring(0, 8);

		return result;
	}

	/**
	 * 计算CVN时使用二个64位的验证密钥，KeyA和KeyB。 a) 计算CVN 的数据源包括：
	 * 主账号（PAN）、卡失效期和服务代码，从左至右顺序编排。 例如19位PAN、4位卡失效期和3位服务代码组成26个字符CVN数据源。 b)
	 * 将上述数据源扩展成128 位二进制数据（不足128 位右补二进制0）。 c) 将128 位二进制数据分成两个64 位的数据块。最左边的64
	 * 位为Block1，最右边的64 位为 Block2。 d) 使用KeyA 对Block1 进行加密。 e) 将Block1
	 * 的加密结果与Block2 进行异或。使用KeyA 对异或结果进行加密。 f) 使用KeyB 对加密结果进行解密。 g) 使用KeyA
	 * 对解密结果进行加密。 h) 从左至右将加密结果中的数字（0－9）抽出，组成一组数字。 i) 从左至右将加密结果中的字符（A－F）抽出，减10
	 * 后将余数组成一组数字，排列在步骤（8） 的数字之后。 j) 步骤（9）的左边第一组三位数即为CVN 值
	 *
	 * @param pan
	 * @param invalidDate
	 * @param serviceCode
	 * @param hexKey
	 *            :验证密钥
	 * @return
	 * @throws Exception
	 */
	public static String generateCVN(String pan, String invalidDate,
									 String serviceCode, String hexKey) throws Exception {

		if (null == pan || null == invalidDate || null == serviceCode
				|| null == hexKey) {
			throw new IllegalArgumentException("卡号或是失效日期或服务代码或验证密钥为空!");
		}

		if (hexKey.length() != 32) {
			throw new IllegalArgumentException("验证密钥长度非32位!");
		}

		String keyA = hexKey.substring(0, 16);

		String keyB = hexKey.substring(16);

		StringBuffer cvnDataSource = new StringBuffer();

		cvnDataSource.append(pan).append(invalidDate).append(serviceCode);

		String cvnDS = StringUtil.padding(cvnDataSource.toString(), "right",
				"0", 32);

		String blockA = cvnDS.substring(0, 16);

		String blockB = cvnDS.substring(16);

		String keyAEncryptBlockAResult = EncryptUnit.decEncNoPaddingDES(keyA,
				blockA, Cipher.ENCRYPT_MODE);

		String xorBlockBResult = StringUtil
				.XOR(keyAEncryptBlockAResult, blockB);

		String result = EncryptUnit.decEncNoPaddingDES(keyA, xorBlockBResult,
				Cipher.ENCRYPT_MODE);

		result = EncryptUnit.decEncNoPaddingDES(keyB, result,
				Cipher.DECRYPT_MODE);

		result = EncryptUnit.decEncNoPaddingDES(keyA, result,
				Cipher.ENCRYPT_MODE);

		String numberData = StringUtil.extract(result, true);

		String characterData = StringUtil.extract(result, false);

		characterData = StringUtil.divide(characterData);

		result = numberData + characterData;

		if (result.length() < 3) {
			throw new IllegalArgumentException("计算CVN返回的长度小于3位长度不正确");
		}

		result = result.substring(0, 3);

		return result;
	}

	/**
	 * 印刷在签名条的右上方处并放在卡号（后4位）后
	 *
	 * 生成CVN2其实就是把服务码变成常数000即可
	 *
	 * @param pan
	 *            :卡号
	 * @param invalidDate
	 *            :失效日期
	 * @param serviceCode
	 *            :服务码
	 * @param hexKey
	 *            :验证密钥
	 * @return
	 * @throws Exception
	 */
	public static String generateCVN2(String pan, String invalidDate,
									  String serviceCode, String hexKey) throws Exception {

		return generateCVN(pan, invalidDate, "000", hexKey);
	}

	/**
	 * 生成过程密钥
	 *
	 * @param pan
	 * @param panSN
	 * @param hexATC
	 * @param mainKey
	 * @return
	 * @throws Exception
	 */
	private static String generateProcesKey(String pan, String panSN,
											String hexATC, String mainKey) throws Exception {

		int cardNoLength = pan.length();

		if (cardNoLength < 14) {
			pan = StringUtil.padding(hexATC, "left", "0", 14);
		}

		String cardNoRight14 = pan.substring(cardNoLength - 14);

		// 分散因子
		String dispersionFactor = cardNoRight14 + panSN;

		// 对分散因子取反
		String reversDispersionFactor = StringUtil
				.reversBytes(dispersionFactor);

		StringBuffer dispersionBuffer = new StringBuffer();

		dispersionBuffer.append(dispersionFactor)
				.append(reversDispersionFactor);

		// 生成子密钥
		String subKey = EncryptUnit.encryptDesSede(mainKey,
				dispersionBuffer.toString());

		String paddATC = StringUtil.padding(hexATC, "left", "0", 16);

		String reversATC = StringUtil.reversBytes(hexATC);

		String paddReversATC = StringUtil.padding(reversATC, "left", "0", 16);

		String mergerATC = paddATC + paddReversATC;

		byte[] tmp = ByteTool.hexStr2Bytes(subKey);
		tmp = EncryptUnit.build3DesKey(tmp);
		// 生成过程密钥
		String processKey = EncryptUnit.encryptDesSede(ByteTool.byte2hex(tmp),
				mergerATC);

		return processKey;
	}

	/**
	 * 计算MAC处理
	 *
	 * @param processKey
	 *            :过程密钥
	 * @param macDataSource
	 *            :计算MAC的数据源
	 * @return
	 * @throws Exception
	 */
	private static String process(String processKey, String macDataSource)
			throws Exception {

		if (null == processKey || processKey.equals("")
				|| processKey.length() != 32) {
			throw new IllegalArgumentException("过程密钥不能为空或不够32位!");
		}

		String leftKey = processKey.substring(0, 16);

		String rightKey = processKey.substring(16);

		// 拆分MAC数据源，每组16位hex(8 byte())
		String[] ds = splitData(macDataSource);

		String des = "";

		for (int i = 0; i < ds.length; i++) {
			if (i == 0) {
				// 第一次只做DES加密
				des = EncryptUnit.decEncNoPaddingDES(leftKey, ds[i],
						Cipher.ENCRYPT_MODE).toUpperCase();
			} else {
				// 用上一次 DES加密结果对 第 i 组数据做异或
				des = StringUtil.XOR(des, ds[i]);
				// 对异或后的数据做DES加密
				des = EncryptUnit.decEncNoPaddingDES(leftKey, des,
						Cipher.ENCRYPT_MODE).toUpperCase();
			}
		}
		// DES 加密最终结果用processKey后16位解密
		des = EncryptUnit
				.decEncNoPaddingDES(rightKey, des, Cipher.DECRYPT_MODE)
				.toUpperCase();

		// 解密后 再用processKey前16位加密
		des = EncryptUnit.decEncNoPaddingDES(leftKey, des, Cipher.ENCRYPT_MODE)
				.toUpperCase();

		return des;

	}

	/*
	 * 将hexMacDataSource进行分组 每 16 字符 8byte 一组
	 */
	private static String[] splitData(String hexMacDataSource) {
		int len = 0;

		int modValue = hexMacDataSource.length() % 16;

		if (modValue == 0) {
			// 补上80000000000000
			hexMacDataSource += "80000000000000";
			len = hexMacDataSource.length() / 16;
		} else if (modValue == 14) {
			// 补上80
			hexMacDataSource += "80";
			len = hexMacDataSource.length() / 16;
		} else {
			hexMacDataSource += "80";
			int hexSrcDataLen = hexMacDataSource.length();
			int totalLen = hexSrcDataLen + (16 - modValue - 2);
			hexMacDataSource = StringUtil.padding(hexMacDataSource, "right",
					"0", totalLen);
			len = hexMacDataSource.length() / 16;

		}

		String[] ds = new String[len];

		for (int i = 0; i < ds.length; i++) {
			if (hexMacDataSource.length() >= 16) {
				ds[i] = hexMacDataSource.substring(0, 16);
				hexMacDataSource = hexMacDataSource.substring(16);
			} else {
				throw new IllegalArgumentException("填充的数据非法!");
			}
		}
		return ds;
	}

}
